import 'package:my_flutter_core/extensions/date_time_extension.dart';
import 'package:my_flutter_core/utils/date_time_utils.dart';
import 'package:my_flutter_core/utils/format_utils.dart';

/// The token holds the necessary data to interact with Spring Security Rest server.
class Token {
  Token({
    this.accessToken,
    this.refreshToken,
    this.bearer,
    this.dateGenerated,
    this.dateExpired,
    this.expiresIn,
    this.dateRetrieved,
    this.localDateExpired,
  });

  // These values come from server.
  final String? accessToken;
  final String? refreshToken;
  final String? bearer;
  final DateTime? dateGenerated;
  final DateTime? dateExpired;
  final int? expiresIn;

  // These generated by the app.
  // We need these for checking token expiration.
  // Consider the app might be used in different time zone with server.
  // With this circumstance check on server dateExpired value will be inappropriate.
  final DateTime? dateRetrieved;
  final DateTime? localDateExpired;

  // The token retrieving from server takes time. We need to consider the time delay when we generate [localDateExpired].
  // The final result would be [localDateExpired] is [_localExpirationCheckBufferInSeconds] seconds before [dateExpired].
  // For example (the result from local testing):
  // [dateGenerated] = 2019-10-24 17:36:10.000
  // [dateRetrieved] = 2019-10-24 17:36:10.182880
  // The network always takes time. That's why we need to add the buffer when we check expiration.
  // We need to take 2019-10-24 17:36:00.182880 as local expiration date.
  static const int _localExpirationCheckBufferInSeconds = 10;

  /// Works with [LocalStorage] and the [json] Map format defined by us.
  factory Token.fromJson(Map<String, dynamic> json) {
    return Token(
      accessToken: json['accessToken']?.toString(),
      refreshToken: json['refreshToken']?.toString(),
      bearer: json['bearer']?.toString(),
      expiresIn: json['expiresIn']?.toInt(),
      dateGenerated: tryToConvert<DateTime>(
        json['dateGenerated'],
        (p) => parseDate(p),
      ),
      dateExpired: tryToConvert<DateTime>(
        json['dateExpired'],
        (p) => parseDate(p),
      ),
      dateRetrieved: tryToConvert<DateTime>(
        json['dateRetrieved'],
        (p) => parseDate(p),
      ),
      localDateExpired: tryToConvert<DateTime>(
        json['localDateExpired'],
        (p) => parseDate(p),
      ),
    );
  }

  /// Works with server and the [json] Map format defined by Security Rest.
  factory Token.fromServerJson(Map<String, dynamic> json) {
    final now = getCurrentDateTime();

    // The token retrieving from server takes time. We need to consider the time delay when we generate [localDateExpired].
    int localDateExpiresIn =
        json['expiresIn'] - _localExpirationCheckBufferInSeconds;

    return Token(
      accessToken: json['accessToken']?.toString(),
      refreshToken: json['refreshToken']?.toString(),
      bearer: json['bearer']?.toString(),
      expiresIn: json['expiresIn']?.toInt(),
      dateGenerated: DateTime.parse(json['generatedAt']).toLocal(),
      dateExpired: DateTime.parse(json['expiredAt']).toLocal(),
      dateRetrieved: now,
      localDateExpired: now.add(Duration(seconds: localDateExpiresIn)),
    );
  }

  /// Converts to [Map<String, dynamic>] and works with [LocalStorage].
  Map<String, dynamic> toJson() {
    return {
      'accessToken': accessToken,
      'refreshToken': refreshToken,
      'bearer': bearer,
      'expiresIn': expiresIn,
      'dateGenerated': dateGenerated.formatLongDateTime(),
      'dateExpired': dateExpired.formatLongDateTime(),
      'dateRetrieved': dateRetrieved.formatLongDateTime(),
      'localDateExpired': localDateExpired.formatLongDateTime(),
    };
  }
}
